上篇文章中我們使用委派實作了事件處理機制,因為使用委派來實作非常方便,可以幫我們接受多筆客戶訂閱又能集結成冊,而執行委派又能實踐通知訂閱者者,一次幫助我們解決許多問題。
但是委派要能讓外部客戶訂閱,就要能被外部使用,所以委派也有可能被外部執行而送出意料之外的通知。
而這個問題並不是private、protected這類存取修飾字可以解決的,所以在C#中,提供了event關鍵字來解決這個問題,讓委派能被外部加入方法,又能禁止被外部執行。
用法很簡單,只要在之前的程式碼上,在使用我們自定義的委派型別前加上event
就可以了,程式碼如下:
class 報社
{
public delegate void 通知對象(string 新聞報導);
public event 通知對象 最新新聞; //只有這一行有更改
public void 投稿新聞(string 新聞稿)
{
最新新聞.Invoke(新聞稿);
}
}
加上event後,C#編譯器就會禁止外部來執行這個委派。
當要執行委派,要記的檢查有沒有人訂閱(新聞事件),若沒人訂閱委派,我們的訂閱名冊就會是null,執行時就會出現例外Null Reference Exception
若者個委派可能在內部多個地方都有可能觸發到,那可以包裝成一個方法,在C#與.NET中似乎習慣將觸發事件方法前面加上On
,程式碼如下:
C# 6.0可以使用?來簡化,是否為null的判斷:
最新新聞?.Invoke(新聞);
C#編譯器也會為我們提示
在以上範例能看到使用者訂閱新聞的過程,想當然地,一個使用者也能訂閱多個新聞,所以觸發事件的委派本身,要能讓訂閱者知道這是哪一間報社通知的新聞,所以.NET中會把委派本身當成參數傳給訂閱者的通知方法,讓使用者知道這次的通知是由哪個報社派送的。
public delegate void 通知對象(報社 有間報社 ,string 新聞報導);
另外通知方法能接收的參數有多有少,每個委派的設定略有不同,我們可以將參數集合起來包裝成一個類別來傳遞。這樣以後要傳遞的參數需要修改或是數量增減也比較方便調整。
public class 新聞
{
public string 標題;
public string 內容;
}
如此一來,委派型別就可以精簡成只要兩個參數就好。
public delegate void 通知對象(報社 有間報社 ,新聞 新聞報導);
上一節一些小改善
可能會讓人納悶是否真的要如此,其實只是為了這一節做鋪陳。事件用的委派型別既然只要兩個參數就好的話,.NET類別庫本身也提供了現成的已經定義好的委派
public delegate void EventHandler(object sender, EventArgs eventArgs);
sender就是來源物件,型別是最廣泛的object,而傳遞參數集合宣告的型別是.NET類別庫定義好的EventArgs。所以要使用這個定義好的委派EventHandler,就要將我們定義好的新聞類別繼承自EventArgs。
但是訂閱者的通知方法可能要自己轉型,最後發行類別與訂閱類別就可以寫成如下:
class 報社
{
public string 名稱;
public event EventHandler 最新新聞;
public void 投稿新聞(string 訊息)
{
新聞 new新聞 = new 新聞() { 標題 = "最新快訊", 內容 = 訊息 };
On收到最新新聞時(this, new新聞);
}
protected void On收到最新新聞時(報社 報社, 新聞 新聞)
{
最新新聞?.Invoke(報社, 新聞);
}
}
class 訂閱者
{
public string 名字 ;
public void 通知我(object sender , EventArgs eventArgs)
{
報社 報社 = sender as 報社;
新聞 新聞 = eventArgs as 新聞;
Console.WriteLine($"我是{名字},我已經收到來自{來源.名稱}的{新聞.標題}:{新聞.內容}");
}
}
執行結果:
主程式:
也可以說委派是事件的基礎,就算使用.NET提供的EventHandler,但背後實作還是依靠委派delegate。比較不同的地方在於,使用event關鍵字來限制委派的部分功能,讓委派不會被外部執行,但本質上還是委派,只是多了一道鎖。
另外事件的目的也只是單向傳遞,所以委派接受的方法並不會有回傳值,就像郵差派送出去也是送後不理,要回信要自己另外寄。當然一方面的原因,也是因為委派本來就不能接收多個方法的回傳值,只能接受一個方法的回傳。
這是我的FB粉專,歡迎大家來按讚:長庚的作業簿
還有我的部落格:https://dannyliu.me
本次內容參考自《C#本事》